import time
import os
import json
from base_logger import get_logger, get_color_print


# Python中可以对函数的参数做类型标识，但是实际上使用的时候对传入的参数类型没有限制
# 等效于：def action_exit(code=0):
def action_exit(code: int = 0):
    if code != 0:
        # 这样 % 的形式是Python的字符串格式化操作，详情可以bing一下
        logger.error('正在退出...(代码%s)' % code)
    else:
        logger.info('正在退出...(代码0)')
    exit(code)


# 自定义一个异常类型
class ValueLimitError(Exception):
    pass


class Subject:
    def __init__(self, name: str = "", score: float = 0.0):
        self.name, self.score = name, score

    def from_object(self, obj: dict):
        try:
            self.name, self.score = obj['name'], obj['score']
        except KeyError:
            logger.warning('读取文件错误! 请检查文件格式.')
            action_exit(1)
        return self

    def to_json(self) -> dict:
        return {
            'name': self.name, 'score': self.score
        }

    # 这个内置函数可以在str()这样的类型转换的时候被调用
    def __str__(self):
        return "[%-5s]%-12s" % (self.name, self.score)


class Student:
    def __init__(self, number="", name: str = "", scores=None):
        self.number, self.name, self.scores = number, name, scores if scores is not None else []

    def add_subject(self, subject: Subject):
        found = False
        # 名字重复就覆盖
        for i in range(len(self.scores)):
            if self.scores[i].name == subject.name:
                self.scores[i] = subject
                found = True
                break
        if not found:
            self.scores.append(subject)

    def from_object(self, obj: dict):
        try:
            self.number, self.name, self.scores = obj['number'], obj['name'], obj['scores']
            # 这行是python的一种语法糖，
            # [xx for xx in xxx]能够直接构成一个列表，后面的if起到筛选作用
            self.scores = [Subject().from_object(score) for score in self.scores if type(score) is dict]
        except KeyError:
            # KeyError是对于dict类型访问位置key的时候的异常
            logger.warning('读取文件错误! 请检查文件格式.')
            action_exit(1)
        return self

    def to_json(self) -> dict:
        return {
            'number': self.number,
            'name': self.name,
            'scores': [score.to_json() for score in self.scores]
        }

    def __str__(self):
        scores_str = ''
        for score in self.scores:
            scores_str += str(score)
        return '[%-10s][%-6s]%s' % (self.number, self.name, scores_str)


# 全局的一个数组，储存当前的所有学生的信息
stu_data = []
# 之前输入过的课程信息
last_subjects = []


# 封装一些常用的提醒操作
def notice_retry(message: str):
    color_print.error("输入错误: %s. 请重试." % message)


def notice_confirm(message: str) -> bool:
    color_print.warning("%s [Y/n]: " % message)
    choice = input().lower()
    if choice not in ['y', 'n']:
        notice_retry("请输入 Y 或者 N, 不区分大小写")
        return notice_confirm(message)
    return True if choice == 'y' else False


def menu() -> int:
    # 在这里延时是因为logger输出需要时间，这时候如果直接用print和input会打乱格式
    time.sleep(0.2)
    op = input('''可用的操作：
1. 输入数据
2. 格式化输出统计的学生成绩
3. 保存当前内容到磁盘
4. 从磁盘读取学生信息
0. 退出
请输入您的选择：''')
    limit = (0, 5)
    # 注意python的try...except结构
    try:
        op = int(op)
        if op not in range(0, 5):
            raise ValueLimitError
    except ValueError:
        notice_retry("期望输入 int 类型数据")
        # 递归地重试
        return menu()
    except ValueLimitError:
        notice_retry("数据不在范围内(%s~%s)" % (limit[0], limit[1] - 1))
        return menu()
    return op


def action_input_data():
    global last_subjects
    # 延迟一下，防止log输出混乱
    time.sleep(0.2)
    number = input('学号: ')
    name = input('姓名: ')
    found = False
    stu = None
    stu_new = True
    for s in stu_data:
        if s.number == number:
            found = True
            stu_new = False
            stu = stu_data[stu_data.index(s)]
            break
    if not found:
        stu = Student(name=name, number=number)
    subject_names = last_subjects
    if len(last_subjects) != 0:
        if not notice_confirm('是否使用上一次的课程数据?'):
            last_subjects = []
    if len(last_subjects) == 0:
        while True:
            subjects_len = input('课程门数: ')
            try:
                subjects_len = int(subjects_len)
            except ValueError:
                notice_retry("期望输入 int 类型数据")
                continue
            break
        subject_names = []
        for i in range(subjects_len):
            subject_names.append(input('课程%s名字: ' % (i + 1,)))
        color_print.info('课程数据输入完成')
        last_subjects = subject_names
        time.sleep(0.2)
    i = 0
    while i < len(subject_names):
        name = subject_names[i]
        score = input('%s的成绩: ' % name)
        try:
            score = float(score)
        except ValueError:
            notice_retry("期望输入 float 类型数据")
            i -= 1
            continue
        stu.add_subject(Subject(name, score))
        i += 1
    del i
    if stu_new:
        stu_data.append(stu)
    if notice_confirm("是否继续添加数据?"):
        action_input_data()


def action_output_data():
    for stu in stu_data:
        color_print.info(str(stu))


# 关于Python的可选参数的使用
def action_save(filename_=None):
    if filename_ is None or (filename_ is not None and len(filename_) > 0):
        time.sleep(0.2)
        filename = input('输入保存文件名(默认save.json):')
    else:
        filename = filename_
    if len(filename) == 0:
        filename = 'save.json'
    if not filename.lower().endswith('.json'):
        filename = filename + '.json'
    with open(filename, 'w') as f:
        json.dump(stu_data, f, default=lambda x: x.to_json())


def action_read(filename_=None):
    global stu_data
    if len(stu_data) > 0:
        color_print.warning("当前正在处理 %s 个学生的数据!" % len(stu_data))
        if notice_confirm('是否先保存当前数据?'):
            action_save()
    while True:
        if filename_ is None or (filename_ is not None and len(filename_) > 0):
            time.sleep(0.2)
            filename = input('输入读取文件名(默认save.json):')
        else:
            filename = filename_
        if len(filename) == 0:
            filename = 'save.json'
        if os.path.exists(filename):
            break
        logger.warning('找不到文件 %s !' % filename)
    try:
        with open(filename, 'r') as f:
            try:
                data = json.load(f)
            except json.decoder.JSONDecodeError:
                logger.error('JSON文件解码错误!')
                action_exit(1)
            data = [Student().from_object(stu) for stu in data]
            stu_data = data
    except IOError:
        logger.warning('读取文件错误! 请检查磁盘.')
        action_exit(1)


def main():
    logger.debug('程序启动.')
    # 类似函数指针数组的操作(实际上不是指针，Python没有指针
    funcs = [
        action_exit,
        action_input_data,
        action_output_data,
        action_save,
        action_read
    ]
    while True:
        op = menu()
        funcs[op]()
        time.sleep(0.2)
        color_print.info('操作成功完成.')


logger = get_logger(__name__)
# 用不同的标签来区分设置
color_print = get_color_print(__name__ + "_")
if __name__ == "__main__":
    main()
